/*
 * @Date         : 2024-05-13 15:47:30 星期1
 * @Author       : xut
 * @Description  :
 */
import { resolve } from "node:path"
import grpc from "@grpc/grpc-js"
import { loadProto } from "../utils/loadProto.js"

function unaryEchoCall(client) {
  return new Promise((resolve, reject) => {
    const call = client.unaryEcho({ name: "tom" }, (error, res) => {
      if (error) {
        // error = {"code":1,"details":"Cancelled on client","metadata":{}}
        console.log(`Error code: ${error.code}, message: ${error.details}`)
      }
      if (res) {
        console.log(`Received response ${JSON.stringify(res)}`)
      }
      resolve()
    })
  })
}

function streamingCall(client) {
  return new Promise((resolve, reject) => {
    const call = client.ServerStreamingEcho()

    let result = ""
    call.on("data", (data) => {
      result += data.value
    })

    /*****************************************************
     * 1. 客户端触发取消 call.cancel()
     * 2. 会触发服务端 cancelled 事件，服务端关闭连接，并通知客户端
     * 3. 然后客户端依次触发 error / status / end 事件，可以通过在 error.code 或 status.code 获取请求取消状态
     *****************************************************/
    call.on("error", (error) => {
      // error =  {"code":1,"details":"Cancelled on client","metadata":{}}
      console.log(`Received unexpected error ${JSON.stringify(error)}`)
    })

    call.on("status", (status) => {
      // status =  {"code":1,"details":"Cancelled on client","metadata":{}}
      console.log(
        `Received status with code=${status.code} details=${status.details}`,
        JSON.stringify(status)
      )
      resolve()
    })

    call.on("end", () => {
      console.log(`Received end, result: `, result)
    })

    /*****************************************************
     * 客户端是没有 cancelled 事件，只会通过 error 事件监听取消
     *****************************************************/
    call.on("cancelled", () => {
      console.log(`Received cancelled`)
    })
  })
}

// 配置retry参数
// 参考:  https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config
const channelOptions = {
  "grpc.enable_retries": 1, // 默认开启，设置 0 关闭重试功能
  "grpc.service_config": JSON.stringify({
    methodConfig: [
      {
        // service这里语法为<package>.<service> package就是proto文件中指定的package，service也是proto文件中指定的 Service Name。
        // method 可以不指定 即当前service下的所以方法都使用该配置。
        // name: [{ service: "echo.Echo", method: "UnaryEcho" }],
        name: [{ service: "echo.Echo" }],
        retryPolicy: {
          // 1次正常发送+3次失败重试, 每次重试都有机会在不同于最初失败的子通道上发送
          maxAttempts: 4,
          // 初次重试时间间隔: random(0, initialBackoff) 秒
          // 第n次重试时间; random(0, min(initialBackoff*backoffMultiplier^(n-1), maxBackoff)) 秒
          initialBackoff: "1s",
          maxBackoff: "10s",
          backoffMultiplier: 2,
          // 只有在 UNAVAILABLE 状态下发起重试
          retryableStatusCodes: ["UNAVAILABLE"],
        },
      },
    ],
  }),
}

async function main() {
  const PROTO_PATH = resolve(process.cwd(), "./echo.proto")
  const echoProtoDescriptor = loadProto(PROTO_PATH)
  const echoProto = echoProtoDescriptor.echo
  const client = new echoProto.Echo(
    "localhost:50051",
    grpc.credentials.createInsecure(),
    channelOptions
  )

  await unaryEchoCall(client)
  console.log("==============================")
  await streamingCall(client)
}

main()
